package com.lsp.microarticle.article.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.lsp.microarticle.article.dao.ArticleDao;
import com.lsp.microarticle.article.openfeign.ArticleFeignService;
import com.lsp.microarticle.article.params.AddArticleParam;
import com.lsp.microarticle.article.params.ArticlePageParam;
import com.lsp.microarticle.article.params.ArticleSearchParam;
import com.lsp.microarticle.article.params.UpdateLikeNumParam;
import com.lsp.microarticle.article.service.ArticleLikeService;
import com.lsp.microarticle.article.service.ArticleService;
import com.lsp.microarticle.article.service.EsArticleService;
import com.lsp.microarticle.article.vo.ArticleVo;
import com.lsp.microarticle.article.vo.EsPage;
import com.lsp.microarticle.article.vo.OneArticleInfoVo;
import com.lsp.microcommon.common.CommonResult;
import com.lsp.microcommon.common.PageVo;
import com.lsp.microcommon.constants.CommonConstants;
import com.lsp.microcommon.constants.PointTypeEum;
import com.lsp.microcommon.entity.Article;
import com.lsp.microcommon.entity.ArticleLike;
import com.lsp.microcommon.entity.Point;
import com.lsp.microcommon.exception.CommonException;
import com.lsp.microcommon.utils.*;
import io.seata.spring.annotation.GlobalTransactional;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

@Service
public class ArticleServiceImpl implements ArticleService {

    @Resource
    private ArticleDao articleDao;

    @Resource
    private ArticleLikeService articleLikeService;

    @Resource
    private ArticleFeignService articleFeignService;

    @Resource
    private EsArticleService esArticleService;

    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    //    seata提供的分布式事务的注解
    @GlobalTransactional(rollbackFor = Exception.class)
    @Override
    public void addArticle(AddArticleParam param) {
        try {
            BaiduTextCheckUtils.textCheck(param.getTitle() + param.getContent());
        } catch (Exception e) {
            throw new CommonException(e.getMessage());
        }

        List<String> imgList = param.getImgList();
        Integer uid = UserUtils.getUser();
        Article article = new Article();
        BeanUtils.copyProperties(param, article);
//        根据指定的分隔符，拼接列表中的字符串
        String imgPath = String.join(",", imgList);
        article.setImgPath(imgPath);
        article.setUid(uid);
        articleDao.insert(article);
//        异步导入es中
        threadPoolExecutor.execute(() ->
        {
            article.setCreateTime(new Date());
            esArticleService.addArticleInfo(article);
        });
//        调用user服务，送积分
        Point point = new Point();
        point.setPoint(5);
        point.setPointType(PointTypeEum.TYPE_ARTICLE.getType());
        point.setUid(UserUtils.getUser());

        CommonResult commonResult = articleFeignService.remoteArticleAddPoint();
        if (commonResult.getCode() == CommonConstants.ERROR_CODE) {
            throw new CommonException(commonResult.getMsg());
        }


    }

    @Override
    public String uploadImg(MultipartFile file) {

        String filename = file.getOriginalFilename();
        String uuid = UUID.randomUUID().toString().replace("-", "");
        filename = uuid + filename.substring(filename.lastIndexOf("."));
        String path = "";
        try {
//            调用工具类将文件上传到oss，返回图片的全路径
            path = OssUtils.uploadFile2OSS(file.getInputStream(), filename);
        } catch (IOException e) {
            e.printStackTrace();
            throw new CommonException("上传文件异常");
        }
        return path;

    }

    @Override
    public PageVo<Article> articlePageInfo(ArticlePageParam param) {
        System.out.println(param);
        Page<Article> page = new Page<>(param.getPageNum(), param.getPageSize());

        if (param.getType().equals("syn")) {
            if (param.getContent() == null || param.getContent() == "") {
                Page<Article> page1 = articleDao.selectPage(page, null);
                PageVo<Article> pageVo = new PageVo<>();
                pageVo.setTotal(page1.getTotal());
                pageVo.setDataList(page1.getRecords());
                return pageVo;
            }
            Page<Article> page2 = articleDao.selectPage(page,
                    new QueryWrapper<Article>().like("content", param.getContent()));
            PageVo<Article> pageVo = new PageVo<>();
            pageVo.setTotal(page2.getTotal());
            pageVo.setDataList(page2.getRecords());
            return pageVo;
        }

        if (param.getContent() == null || param.getContent() == "") {
            Page<Article> page1 = articleDao.selectPage(page,
                    new QueryWrapper<Article>()
                            .eq("type", param.getType()));

            PageVo<Article> pageVo = new PageVo<>();
            pageVo.setTotal(page1.getTotal());
            pageVo.setDataList(page1.getRecords());
            return pageVo;
        }
        Page<Article> page2 = articleDao.selectPage(page,
                new QueryWrapper<Article>()
                        .eq("type", param.getType())
                        .like("content", param.getContent()));
        PageVo<Article> pageVo = new PageVo<>();
        pageVo.setTotal(page2.getTotal());
        pageVo.setDataList(page2.getRecords());
        return pageVo;

    }

    @Override
    public PageVo<ArticleVo> articlePage(ArticleSearchParam param) {
        Long total = 0L;
        List<ArticleVo> dataList = new ArrayList<>();

        if(param.getSearchInfo()!= null && param.getSearchInfo() != "") {
//            通过es搜索数据
            EsPage<Article> articleEsPage = esArticleService.searchArticle(param);
//            获取es中存储的数据，该数据没有今天之前的的点赞数
            List<Article> dataList1 = articleEsPage.getDataList();
//            根据获取的id查询数据库，获取当天之前的点赞数
            List<Integer> idList = dataList1.stream()
                    .map(item -> item.getId())
                    .collect(Collectors.toList());
//            获取的数据包含文章当天之前的点赞总数
            List<Article> articles = articleDao.selectBatchIds(idList);
            dataList = getArticleVolIST(articles);
        } else {
//        给后续的sql语句加limit
            Page<Article> page = new Page<Article>(param.getPageNum(), param.getPageSize());
//        执行sql，分页查询
            Page<Article> articlePage = articleDao.selectPage(page,
                    new LambdaQueryWrapper<Article>()
                            .orderByDesc(Article::getCreateTime));
//        获取total
            total = articlePage.getTotal();
//        获取返回的列表
            List<Article> records = articlePage.getRecords();
//        由于返回到前端的是ArticleVo，所以需要把records copy 到 voList
//        List<ArticleVo> voList = new ArrayList<>();
            dataList = getArticleVolIST(records);
        }

//        返回前端的数据包括total，pageVo
        PageVo<ArticleVo> pageVo = new PageVo<>();
        pageVo.setTotal(total);
        pageVo.setDataList(dataList);
        return pageVo;
    }

//    设置第一张做封面，同时获取全部点赞数
    private List<ArticleVo> getArticleVolIST(List<Article> articleList) {
//        通过map设置返回的对象为vo
        List<ArticleVo> articleVosList = articleList.stream().map(item -> {
            ArticleVo articleVo = new ArticleVo();
            BeanUtils.copyProperties(item, articleVo);
            String imgPath = item.getImgPath();
//            使用第一张图做封面
            if (StringUtils.isNoneBlank(imgPath)) {
                articleVo.setCoverImg(imgPath.split(",")[0]);
            }
            /**
             *  查看当前用户是否点赞
             */
            Integer uid = UserUtils.getUser();
//        先查找该用户的点赞记录
            List<ArticleLike> articleLikesList = articleLikeService.articleLikeList(uid);
//        提取出articleId
            List<Integer> articleIdList = articleLikesList.stream().map(ArticleLike::getArticleId).collect(Collectors.toList());
            for (Integer articleId : articleIdList) {
                if (articleId.equals(item.getId())) {
                    articleVo.setLikeFlag(1);
                }
            }
            /**
             * 查找该文章的所有点赞数
             *      当天的redis中存的点赞数 + 数据库的点赞数
             */
//            List<ArticleLike> likes = articleLikeService.likeList();
////            提取出文章id
//            List<Integer> allArticleIdList2 = likes.stream()
//                    .map(ArticleLike::getArticleId)
//                    .collect(Collectors.toList());
//            for (Integer articleId : allArticleIdList2) {
//                if (articleId.equals(item.getId())) {
//                    articleVo.setLikeNum(articleVo.getLikeNum() + 1);
//                }
//            }
            String yearAndMouthAndDay = DateUtils.getYearAndMouthAndDay();
            String likeNumkey = String.format("like:%s", yearAndMouthAndDay);
            Integer num = (Integer) RedisUtils.hashGet(likeNumkey, articleVo.getId().toString());
            if(num != null) {
                articleVo.setLikeNum(articleVo.getLikeNum() + num);
            }
            return articleVo;
        }).collect(Collectors.toList());
        return articleVosList;
    }

    @Override
    public OneArticleInfoVo selectInfoById(Integer articleId) {
        Article article = articleDao.selectOne(new LambdaQueryWrapper<Article>().eq(Article::getId, articleId));
        OneArticleInfoVo oneArticleInfoVo = new OneArticleInfoVo();
        BeanUtils.copyProperties(article, oneArticleInfoVo);
        String[] pathArr = article.getImgPath().split(",");
        List<String> pathList = new ArrayList<>(Arrays.asList(pathArr));
        oneArticleInfoVo.setCoverImg(pathList);
        return oneArticleInfoVo;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateLikeNumBatch() {
        int batchCount = 100;
//        redis中存储的当天相关文章的点赞总数
        String yearAndMouthAndDay = DateUtils.getLastYearAndMouthAndDay();
        String likeNumkey = String.format("like:%s", yearAndMouthAndDay);
//        获取所有的field 和value
        Map<Object, Object> map = RedisUtils.hashGetAll(likeNumkey);
        List<UpdateLikeNumParam> list = new ArrayList<>();
        for (Object key : map.keySet()) {
            UpdateLikeNumParam likeNumParam = new UpdateLikeNumParam();
            likeNumParam.setArticleId(Integer.valueOf(key.toString()));
            likeNumParam.setLikeNum(Integer.valueOf(map.get(key).toString()));
            list.add(likeNumParam);
            if (list.size() == batchCount) {
//                达到一百，进行批量更新
                articleDao.batchUpdateLikeNum(list);
                list.clear();
            }
        }
        if (list.size() > 0) {
//                对剩余数据进行批量更新
            articleDao.batchUpdateLikeNum(list);
            list.clear();
        }
    }

    @Override
    public List<ArticleVo> articleRank(Integer count) {
        Integer uid = UserUtils.getUser();
//        先查redis，取zset数据，包含的是文章的id和分数
        Set<ZSetOperations.TypedTuple<Object>> typedTuples =
                RedisUtils.zsetReverseRangeWithScore(
                        "article:rank",
                        0,
                        Double.MAX_VALUE,
                        count);
//        redis中的zset没存排序列表
        if(typedTuples==null || typedTuples.size() ==0){
            return new ArrayList<>();
        }
//      获取到文章id
        List<Integer> idList = new ArrayList<>();
        typedTuples.forEach(item -> {
            idList.add(Integer.valueOf(item.getValue().toString()));
        });
//        根据文章id查数据库获取文章信息
        List<Article> articles = articleDao.selectBatchIds(idList);

        List<ArticleVo> articleVoList = articles.stream().map(item -> {
            ArticleVo articleVo = new ArticleVo();
            BeanUtils.copyProperties(item, articleVo);
//            设置封面
            if (StringUtils.isNoneBlank(item.getImgPath())) {
                articleVo.setCoverImg(item.getImgPath().split(",")[0]);
            }
//            查找点赞总数（redis的当天点赞量和数据库的当天之前的点赞量）
            String likeNumKey = String.format("like:%S", DateUtils.getYearAndMouthAndDay());
            Object todayLikeNum = RedisUtils.hashGet(likeNumKey, item.getId().toString());
            if (todayLikeNum != null) {
                articleVo.setLikeNum(articleVo.getLikeNum() + (Integer) todayLikeNum);
            }
//            从点赞记录中，获取当前用户是否点赞
            Object likeInfo = RedisUtils.hashGet("like:" + item.getId(), uid.toString());
            if (likeInfo != null) {
                articleVo.setLikeFlag(1);
            }
            return articleVo;
        }).collect(Collectors.toList());
//        根据分数做排序
        articleVoList.forEach(item -> {
//            查询文章的分数
            ZSetOperations.TypedTuple<Object> objectTypedTuple = typedTuples.stream().
                    filter(t -> item.getId().equals(Integer.valueOf(t.getValue().toString())))
                    .findFirst().orElse(null);
            if (objectTypedTuple != null) {
                item.setScore(objectTypedTuple.getScore());
            }
        });
//        排序方法
        Collections.sort(articleVoList, new Comparator<ArticleVo>() {
            @Override
            public int compare(ArticleVo o1, ArticleVo o2) {
                double v = o2.getScore() - o1.getScore();
//                返回大于0的值，交换顺序
                if (v > 0) {
                    return 1;
                } else {
                    return -1;
                }
            }
        });
        return articleVoList;
    }
}
